package TangramCore;

import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;

public class Polygon2D implements Shape
{
    Point2D points[];
    
    Polygon2D(){
    }

    public Polygon2D(Point2D p[]){
        points = (Point2D[])p.clone();
    }

    public Polygon2D(Polygon2D pol){
        points = new Point2D[pol.points.length];
        for(int i = 0; i < points.length; i++)
            points[i] = new Point2D(pol.points[i]);
    }

    public Point2D[] getPoints(){
        return (Point2D[])points.clone();
    }

    public Point2D[] getRawPoints(){
        return points;
    }

    public void setPoints(Point2D p[]){
        points = (Point2D[])p.clone();
    }

    public void translate(double dx, double dy){
        for(Point2D p: points)
            p.translate(dx, dy);
    }

    public void translate(Point2D pd){
        for(Point2D p: points)
            p.translate(pd);
    }

    public final void rotate(double x, double y, double a){
        for(Point2D p: points)
            p.rotate(x, y, a);
    }

    public final void rotate(Point2D pivot, double a){
        rotate(pivot.x, pivot.y, a);
    }

    public final void rotate(double a){
        for(Point2D p: points)
            p.rotate(a);
    }

    public void copyFrom(Polygon2D pol){
        if(pol.points.length != points.length){
            points = new Point2D[pol.points.length];
            for(int i = 0; i < points.length; i++)
                points[i] = new Point2D(pol.points[i]);
        }
        else {
            for(int j = 0; j < points.length; j++){
                points[j].x = pol.points[j].x;
                points[j].y = pol.points[j].y;
            }
        }
    }
/*
    public double getBoundingRadius(){
        double r = 0.0D;
        for(Point2D p: points){
            double d = p.x * p.x + p.y * p.y;
            if(d > r)
                r = d;
        }
        return Math.sqrt(r);
    }

    public Polygon2D getBoundingBox()
    {
        return getBoundingBox(null);
    }

    public Polygon2D getBoundingBox(Polygon2D polygon2d)
    {
        double d = (-1.0D / 0.0D);
        double d1 = (-1.0D / 0.0D);
        double d2 = (1.0D / 0.0D);
        double d3 = (1.0D / 0.0D);
        for(int i = 0; i < points.length; i++)
        {
            if(points[i].x > d)
                d = points[i].x;
            if(points[i].x < d2)
                d2 = points[i].x;
            if(points[i].y > d1)
                d1 = points[i].y;
            if(points[i].y < d3)
                d3 = points[i].y;
        }

        if(polygon2d == null)
        {
            Point2D apoint2d[] = new Point2D[4];
            apoint2d[0] = new Point2D(d2, d3);
            apoint2d[1] = new Point2D(d2, d1);
            apoint2d[2] = new Point2D(d, d1);
            apoint2d[3] = new Point2D(d, d3);
            polygon2d = new Polygon2D(apoint2d);
        } else
        {
            polygon2d.points[0].setCoordinates(d2, d3);
            polygon2d.points[1].setCoordinates(d2, d1);
            polygon2d.points[2].setCoordinates(d, d1);
            polygon2d.points[3].setCoordinates(d, d3);
        }
        return polygon2d;
    }

    public static final boolean boundingBoxesIntersect(Polygon2D polygon2d, Polygon2D polygon2d1)
    {
        double d2 = polygon2d.points[0].x;
        double d = polygon2d.points[2].x;
        double d3 = polygon2d.points[0].y;
        double d1 = polygon2d.points[1].y;
        double d6 = polygon2d1.points[0].x;
        double d4 = polygon2d1.points[2].x;
        double d7 = polygon2d1.points[0].y;
        double d5 = polygon2d1.points[1].y;
        if(d < d6 || d4 < d2)
            return false;
        return d1 >= d7 && d5 >= d3;
    }*/

    public boolean intersects(Polygon2D pol)
    {
        final double tol = 1.0D;
        double xmax = (-1.0D / 0.0D);
        double ymax = (-1.0D / 0.0D);
        double xmin = (1.0D / 0.0D);
        double ymin = (1.0D / 0.0D);
        for(Point2D p: points){
            if(p.x > xmax)
                xmax = p.x;
            if(p.x < xmin)
                xmin = p.x;
            if(p.y > ymax)
                ymax = p.y;
            if(p.y < ymin)
                ymin = p.y;
        }

        double xmax2 = (-1.0D / 0.0D);
        double ymax2 = (-1.0D / 0.0D);
        double xmin2 = (1.0D / 0.0D);
        double ymin2 = (1.0D / 0.0D);
        for(Point2D p: pol.points){
            if(p.x > xmax2)
                xmax2 = p.x;
            if(p.x < xmin2)
                xmin2 = p.x;
            if(p.y > ymax2)
                ymax2 = p.y;
            if(p.y < ymin2)
                ymin2 = p.y;
        }

        xmax+=tol;
        ymax+=tol;
        xmin-=tol;
        ymin-=tol;
        xmax2+=tol;
        ymax2+=tol;
        xmin2-=tol;
        ymin2-=tol;
        if(xmax < xmin2 || xmax2 < xmin)
            return false;
        if(ymax < ymin2 || ymax2 < ymin)
            return false;
        Point2D points2[] = pol.points;
        for(int k = 0; k < points.length; k++){
            for(int l = 0; l < points2.length; l++)
                if(linesIntersect(points[k], points[(k + 1) % points.length], points2[l], points2[(l + 1) % points2.length]))
                    return true;
        }
        return false;
    }

    public boolean intersects(Rectangle2D r)
    {
        final double tol = 1.0D;
        double xmax = (-1.0D / 0.0D);
        double ymax = (-1.0D / 0.0D);
        double xmin = (1.0D / 0.0D);
        double ymin = (1.0D / 0.0D);
        for(Point2D p: points){
            if(p.x > xmax)
                xmax = p.x;
            if(p.x < xmin)
                xmin = p.x;
            if(p.y > ymax)
                ymax = p.y;
            if(p.y < ymin)
                ymin = p.y;
        }

        double xmax2 = r.getMaxX();
        double ymax2 = r.getMaxY();
        double xmin2 = r.getMinX();
        double ymin2 = r.getMinY();

        xmax+=tol;
        ymax+=tol;
        xmin-=tol;
        ymin-=tol;
        xmax2+=tol;
        ymax2+=tol;
        xmin2-=tol;
        ymin2-=tol;
        if(xmax < xmin2 || xmax2 < xmin)
            return false;
        if(ymax < ymin2 || ymax2 < ymin)
            return false;
        
        
        for(int k = 0; k < points.length; k++){
            Point2D p1 = points[k], p2 = points[(k + 1) % points.length];
            if(linesIntersect(p1.x, p1.y, p2.x, p2.y, xmin2, ymin2, xmax2, ymin2))
                return true;
            if(linesIntersect(p1.x, p1.y, p2.x, p2.y, xmax2, ymin2, xmax2, ymax2))
                return true;
            if(linesIntersect(p1.x, p1.y, p2.x, p2.y, xmax2, ymax2, xmin2, ymax2))
                return true;
            if(linesIntersect(p1.x, p1.y, p2.x, p2.y, xmin2, ymax2, xmin2, ymin2))
                return true;
        }
        return false;
    }

    public Point2D intersectsLineAt(Point2D p1, Point2D p2){
        return intersectsLineAt(p1, p2, null);
    }

    public Point2D intersectsLineAt(Point2D p1, Point2D p2, Point2D ret)
    {
        double dmax = (1.0D / 0.0D);
        double x = 0.0D;
        double y = 0.0D;
        if(ret == null)
            ret = new Point2D();
        for(int i = 0; i < points.length; i++){
            Point2D pi = linesIntersectAt(p1, p2, points[i], points[(i + 1) % points.length], ret);
            if(pi != null){
                double d = p1.distanceSq(pi);
                if(d < dmax){
                    x = pi.x;
                    y = pi.y;
                    dmax = d;
                }
            }
        }

        if(dmax < (1.0D / 0.0D)){
            ret.x = x;
            ret.y = y;
            return ret;
        }
        else
            return null;
    }

    public static final boolean linesIntersect(Point2D p1, Point2D p2, Point2D p3, Point2D p4){
        return linesIntersect(p1.x,p1.y,p2.x,p2.y,p3.x,p3.y,p4.x,p4.y);
    }
    
    public static final boolean linesIntersect(double p1x, double p1y, double p2x, double p2y, double p3x, double p3y, double p4x, double p4y){
        double d4 = p1y - p3y;
        double d5 = p4x - p3x;
        double d6 = p1x - p3x;
        double d7 = p4y - p3y;
        double d8 = p2x - p1x;
        double d9 = p2y - p1y;
        double d2 = d8 * d7 - d9 * d5;
        double d3 = d4 * d5 - d6 * d7;
        if(d2 == 0.0D){
            if(d3 != 0.0D)
                return false;
            double d10;
            double d12;
            if(p1x > p2x){
                d10 = p1x;
                d12 = p2x;
            }
            else {
                d10 = p2x;
                d12 = p1x;
            }
            double d11;
            double d13;
            if(p1y > p2y){
                d11 = p1y;
                d13 = p2y;
            }
            else{
                d11 = p2y;
                d13 = p1y;
            }
            return (d12 <= p3x || d12 <= p4x) && (d10 >= p3x || d10 >= p4x) && (d13 <= p3y || d13 <= p4y) && (d11 >= p3y || d11 >= p4y);
        }
        else{
            double d = d3 / d2;
            double d1 = (d4 * d8 - d6 * d9) / d2;
            return d >= 0.0D && d <= 1.0D && d1 >= 0.0D && d1 <= 1.0D;
        }
    }

    public static final Point2D linesIntersectAt(Point2D point2d, Point2D point2d1, Point2D point2d2, Point2D point2d3)
    {
        return linesIntersectAt(point2d, point2d1, point2d2, point2d3, null);
    }

    public static final Point2D linesIntersectAt(Point2D point2d, Point2D point2d1, Point2D point2d2, Point2D point2d3, Point2D point2d4)
    {
        if(point2d4 == null)
            point2d4 = new Point2D();
        double d4 = point2d.y - point2d2.y;
        double d5 = point2d3.x - point2d2.x;
        double d6 = point2d.x - point2d2.x;
        double d7 = point2d3.y - point2d2.y;
        double d8 = point2d1.x - point2d.x;
        double d9 = point2d1.y - point2d.y;
        double d2 = d8 * d7 - d9 * d5;
        double d3 = d4 * d5 - d6 * d7;
        if(d2 == 0.0D)
        {
            if(d3 != 0.0D)
                return null;
            double d10;
            double d12;
            if(point2d.x > point2d1.x)
            {
                d10 = point2d.x;
                d12 = point2d1.x;
            } else
            {
                d10 = point2d1.x;
                d12 = point2d.x;
            }
            double d11;
            double d13;
            if(point2d.y > point2d1.y)
            {
                d11 = point2d.y;
                d13 = point2d1.y;
            } else
            {
                d11 = point2d1.y;
                d13 = point2d.y;
            }
            if(d12 > point2d2.x && d12 > point2d3.x || d10 < point2d2.x && d10 < point2d3.x || d13 > point2d2.y && d13 > point2d3.y || d11 < point2d2.y && d11 < point2d3.y)
                return null;
            double d16 = point2d.x - point2d3.x;
            if(d6 * d16 <= 0.0D)
            {
                point2d4.copyFrom(point2d);
                return point2d4;
            }
            double d17 = point2d.y - point2d3.y;
            if(d4 * d17 <= 0.0D)
            {
                point2d4.copyFrom(point2d);
                return point2d4;
            }
            double d14 = d6 * d6 + d4 * d4;
            double d15 = d16 * d16 + d17 * d17;
            if(d14 < d15)
            {
                point2d4.copyFrom(point2d2);
                return point2d4;
            } else
            {
                point2d4.copyFrom(point2d3);
                return point2d4;
            }
        }
        double d = d3 / d2;
        double d1 = (d4 * d8 - d6 * d9) / d2;
        if(d < 0.0D || d > 1.0D || d1 < 0.0D || d1 > 1.0D)
        {
            return null;
        } else
        {
            point2d4.setLocation(point2d.x + d * (point2d1.x - point2d.x), point2d.y + d * (point2d1.y - point2d.y));
            return point2d4;
        }
    }

    public boolean contains(double x, double y) {
        boolean flag = false;
        int i = 0;
        for(int j = points.length - 1; i < points.length; j = i++)
            if((points[i].y <= y && y < points[j].y || points[j].y <= y && y < points[i].y) &&
                x < ((points[j].x - points[i].x) * (y - points[i].y)) / (points[j].y - points[i].y) + points[i].x)
                flag = !flag;
        return flag;
    }

    public boolean contains(java.awt.geom.Point2D p){
        return contains(p.getX(), p.getY());
    }

    public static Point2D getNearestPointOnLine(Point2D p, Point2D p1, Point2D p2){
        return getNearestPointOnLine(p, p1, p2, null);
    }

    public static Point2D getNearestPointOnLine(Point2D p, Point2D p1, Point2D p2, Point2D ret){
        if(ret == null)
            ret = new Point2D();
        double dx1 = p1.x - p.x;
        double dy1 = p1.y - p.y;
        double dx2 = p2.x - p.x;
        double dy2 = p2.y - p.y;
        double d1sq = dx1 * dx1 + dy1 * dy1;
        double d4 = (dx2 * dx1 + dy2 * dy1) / d1sq;
        if(d4 <= 0.0D){
            ret.x = p.x;
            ret.y = p.y;
        } else
        if(d4 >= 1.0D){
            ret.x = p1.x;
            ret.y = p1.y;
        }
        else{
            ret.x = p.x + d4 * dx1;
            ret.y = p.y + d4 * dy1;
        }
        return ret;
    }
/*
    public static double getDistanceFromPointToLine(Point2D point2d, Point2D point2d1, Point2D point2d2)
    {
        return getDistanceFromPointToLine(point2d, point2d1, point2d2, null);
    }

    public static double getDistanceFromPointToLine(Point2D point2d, Point2D point2d1, Point2D point2d2, Point2D point2d3)
    {
        point2d3 = getNearestPointOnLine(point2d, point2d1, point2d3);
        return point2d2.getDistanceTo(point2d3);
    }

    public static double getSquaredDistanceFromPointToLine(Point2D point2d, Point2D point2d1, Point2D point2d2)
    {
        return getSquaredDistanceFromPointToLine(point2d, point2d1, point2d2, null);
    }

    public static double getSquaredDistanceFromPointToLine(Point2D point2d, Point2D point2d1, Point2D point2d2, Point2D point2d3)
    {
        point2d3 = getNearestPointOnLine(point2d, point2d1, point2d3);
        return point2d2.getSquaredDistanceTo(point2d3);
    }

    public double getDistanceToPoint(Point2D point2d)
    {
        return getDistanceToPoint(point2d, null);
    }

    public double getDistanceToPoint(Point2D point2d, Point2D point2d1)
    {
        return Math.sqrt(getSquaredDistanceToPoint(point2d, point2d1));
    }

    public double getSquaredDistanceToPoint(Point2D point2d)
    {
        return getSquaredDistanceToPoint(point2d);
    }

    public double getSquaredDistanceToPoint(Point2D point2d, Point2D point2d1)
    {
        double d = (1.0D / 0.0D);
        Point2D point2d2;
        if(point2d1 == null)
            point2d2 = new Point2D();
        else
            point2d2 = point2d1;
        int i = points.length - 1;
        for(int j = 0; j < i; j++)
        {
            double d1 = getSquaredDistanceFromPointToLine(points[j], points[j + 1], point2d, point2d2);
            if(d1 < d)
                d = d1;
        }

        double d2 = getSquaredDistanceFromPointToLine(points[i], points[0], point2d, point2d2);
        if(d2 < d)
            d = d2;
        return d;
    }*/

    public Point2D getNearestPoint(Point2D p){
        return getNearestPoint(p, null);
    }

    public Point2D getNearestPoint(Point2D p, Point2D ret)
    {
        double dmin = (1.0D / 0.0D);
        double x = 0.0D;
        double y = 0.0D;
        if(ret == null)
            ret = new Point2D();
        int i = points.length - 1;
        for(int j = 0; j < i; j++){
            getNearestPointOnLine(points[j], points[j + 1], p, ret);
            double d = p.distanceSq(ret);
            if(d < dmin){
                x = ret.x;
                y = ret.y;
                dmin = d;
            }
        }

        getNearestPointOnLine(points[i], points[0], p, ret);
        double d = p.distanceSq(ret);
        if(d < dmin)
            return ret;
        else{
            ret.x = x;
            ///TODO ??? x???
            ret.y = y;
            return ret;
        }
    }
/*
    public boolean isWithinRangeOfPoint(double d, Point2D point2d)
    {
        return isWithinRangeOfPoint(d, point2d, null);
    }

    public boolean isWithinRangeOfPoint(double d, Point2D point2d, Point2D point2d1)
    {
        double d1 = d * d;
        if(point2d1 == null)
            point2d1 = new Point2D();
        int i = points.length - 1;
        for(int j = 0; j < i; j++)
            if(getSquaredDistanceFromPointToLine(points[j], points[j + 1], point2d, point2d1) < d1)
                return true;

        return getSquaredDistanceFromPointToLine(points[i], points[0], point2d, point2d1) < d1;
    }

    public String toString()
    {
        if(points == null || points.length == 0)
            return "[ ]";
        String s = "[ " + points[0];
        for(int i = 1; i < points.length; i++)
            s = s + ", " + points[i];

        return s + " ]";
    }

    private static final boolean USE_PRECISE_INTERSECTION = true;
 */

    public PathIterator getPathIterator(AffineTransform at) {
        return new PolygonPathIterator(at);
    }
    
    public PathIterator getPathIterator(AffineTransform at, double flatness) {
        return new PolygonPathIterator(at);
    }
    
    private class PolygonPathIterator implements PathIterator {
        AffineTransform transform;
        int index;
        
        PolygonPathIterator(AffineTransform at) {
            transform = at;
        }
        
        public int getWindingRule() {
            return WIND_EVEN_ODD;
        }
        
        public boolean isDone() {
            return index > points.length;
        }
        
        public void next() {
            index++;
        }
        
        public int currentSegment(float[] coords) {
            if (index >= points.length) {
                return SEG_CLOSE;
            }
            coords[0] = (float) points[index].x;
            coords[1] = (float) points[index].y;
            
            if (transform != null) {
                transform.transform(coords, 0, coords, 0, 1);
            }
            return (index == 0 ? SEG_MOVETO : SEG_LINETO);
        }
        
        public int currentSegment(double[] coords) {
            if (index >= points.length) {
                return SEG_CLOSE;
            }
            coords[0] = points[index].x;
            coords[1] = points[index].y;
            
            if (transform != null) {
                transform.transform(coords, 0, coords, 0, 1);
            }
            return (index == 0 ? SEG_MOVETO : SEG_LINETO);
        }
    }

    public boolean contains(Rectangle2D r) {
        if(intersects(r))
            return false;
        double x1 = r.getMinX(), y1 = r.getMinY();
        double x2 = r.getMaxX(), y2 = r.getMaxY();
        if(!contains(x1, y1))
            return false;
        if(!contains(x2, y1))
            return false;
        if(!contains(x2, y2))
            return false;
        return contains(x1, y2);
    }
    
    public boolean intersects(double x1, double y1, double x2, double y2){
        for(int i = 0; i < points.length; i++){
            Point2D p3 = points[i], p4 = points[(i + 1) % points.length];
            if(linesIntersect(x1, y1, x2, y2, p3.x, p3.y, p4.x, p4.y))
                return true;
        }
        return false;
    }
 
    public boolean contains(double x1, double y1, double x2, double y2) {
        return contains(x1, y1) && contains(x2, y2) && !intersects(x1, y1, x2, y2);
    }
    
    public Rectangle2D getBounds2D() {
        return getBounds2D(new Rectangle2D.Double());
    }

    public Rectangle getBounds() {
        return getBounds(new Rectangle());
    }
   
    public Rectangle getBounds(Rectangle r) { //aby se pri posunu zbytecne nezahazovaly objekty
        Point2D[] p = this.getPoints();
        int x1, x2, y1, y2;
        x1 = x2 = (int)p[0].x ;
        y1 = y2 = (int)p[0].y;
        for (int i = 1; i < p.length; i++) {
            x1 = Math.min(x1, (int)p[i].x);
            x2 = Math.max(x2, (int)p[i].x);
            y1 = Math.min(y1, (int)p[i].y);
            y2 = Math.max(y2, (int)p[i].y);
        }
        r.setBounds(x1, y1, x2 - x1, y2 - y1);
        return r;
    }
    
    public Rectangle2D getBounds2D(Rectangle2D r) {
        Point2D[] p = this.getPoints();
        double x1, x2, y1, y2;
        x1 = x2 = p[0].x;
        y1 = y2 = p[0].y;
        for (int i = 1; i < p.length; i++) {
            x1 = Math.min(x1, p[i].x);
            x2 = Math.max(x2, p[i].x);
            y1 = Math.min(y1, p[i].y);
            y2 = Math.max(y2, p[i].y);
        }
        r.setRect(x1, y1, x2 - x1, y2 - y1);
        return r;
    }

}
